package com.gzbd.login.jwt;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.gzbd.login.common.Result;
import com.gzbd.login.config.AuthConfig;
import com.gzbd.login.shiro.validator.SessionValidator;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authz.UnauthenticatedException;
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
import org.apache.shiro.web.util.WebUtils;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RequestMethod;

import javax.servlet.Filter;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.List;

/**
 * @author xlj
 * @version 1.0
 * @description: TODO
 * @date 2024/2/22 15:00
 */
@Slf4j

public class JwtFilter extends BasicHttpAuthenticationFilter  {


    /**
     * shiro相关配置
     */
    private final AuthConfig authConfig;

    /**
     * session校验器
     */
    private final SessionValidator sessionValidator;

    /**
     * token管理器
     */
    private final CustomTokenManager customTokenManager;

    public JwtFilter(AuthConfig authConfig, SessionValidator sessionValidator, CustomTokenManager customTokenManager) {
        this.authConfig = authConfig;
        this.sessionValidator = sessionValidator;
        this.customTokenManager = customTokenManager;
    }

    /**
     * 执行登录
     *
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    @Override
    protected boolean executeLogin(ServletRequest request, ServletResponse response) throws IOException {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        String token = httpServletRequest.getHeader("SevenHee-Token");
        JwtToken jwtToken = new JwtToken(token);
        // 提交给realm进行登入，如果错误他会抛出异常并被捕获
        try {
            getSubject(request, response).login(jwtToken);
            // 如果没有抛出异常则代表登入成功，返回true
            return true;
        } catch (AuthenticationException e) {
            return false;
        }

    }

    /**
     * 执行登录认证
     *
     * @param request
     * @param response
     * @param mappedValue
     * @return
     */
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        try {
            //判断请求的请求头是否带上 "Authorization"
            HttpServletRequest httpServletRequest = (HttpServletRequest) request;
            String token = ((HttpServletRequest) request).getHeader("token");
//            if (isLoginAttempt(request, response)) {
            if(!"".equals(token) && token != null){
                //如果存在，则进入 executeLogin 方法执行登入，检查 token 是否正确
                try {
                    //不需要登录校验的请求
                    if (requestIgnore(httpServletRequest)) {
                        return true;
                    }
                    if (!sessionValidator.checkSession(httpServletRequest)) {
                        //session校验
                        this.responseMsg(response, "你的账号可能在别处登录!", 403);
                        return false;
                    }
                    if (!customTokenManager.checkToken(httpServletRequest)) {
                        return false;
                    }
                    return true;
//                    return executeLogin(request, response);
                } catch (Exception e) {
                    // 认证出现异常，传递错误信息msg
                    String msg = e.getMessage();
                    // 获取应用异常(该Cause是导致抛出此throwable(异常)的throwable(异常))
                    Throwable throwable = e.getCause();
                    if (throwable instanceof SignatureVerificationException) {
                        // 该异常为JWT的AccessToken认证失败(Token或者密钥不正确)
                        msg = "Token或者密钥不正确(" + throwable.getMessage() + ")";
                    } else if (throwable instanceof UnauthenticatedException) {
                        // 该异常为JWT的AccessToken认证失败(Token或者密钥不正确)
                        msg = "不接受该请求";
                        return false;
                    } else {
                        // 应用异常不为空
                        if (throwable != null) {
                            // 获取应用异常msg
                            msg = throwable.getMessage();
                        }
                    }
                    return false;
                }
            }else {

                //不需要登录校验的请求
                if (requestIgnore(httpServletRequest)) {
                    return true;
                }
                //如果请求头不存在 Token，则可能是执行登陆操作或者是游客状态访问，无需检查 token，直接返回 true
                return false;
            }
//            return executeLogin(request, response);
        } catch (Exception e) {
            log.error("JwtFilter过滤验证失败!");
            return false;
        }
    }

    /**
     * 无需转发，直接返回Response信息
     */
    private void responseMsg(ServletResponse response, String msg, int state) {
        HttpServletResponse httpServletResponse = WebUtils.toHttp(response);
        httpServletResponse.reset();
        httpServletResponse.setStatus(HttpStatus.UNAUTHORIZED.value());
        httpServletResponse.setCharacterEncoding("UTF-8");
        httpServletResponse.setContentType("application/json; charset=utf-8");
        try (ServletOutputStream out = httpServletResponse.getOutputStream()) {
            out.write(JSON.toJSONString(ResponseUtil.error(state, msg), SerializerFeature.WriteMapNullValue).getBytes(StandardCharsets.UTF_8));
            out.flush();
        } catch (IOException e) {
//            throw new Exception("直接返回Response信息出现IOException异常:" + e.getMessage());
        }

    }

    /**
     * 认证失败时，自定义返回json数据
     *
     * @param request  请求
     * @param response 响应
     * @return boolean* @throws Exception 异常
     */
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        Result result = Result.fail(-1,"认证失败");
        Object parse = JSONObject.toJSON(result);
        response.setCharacterEncoding("utf-8");
        response.getWriter().print(parse);
        return super.onAccessDenied(request, response);
    }

    /**
     * 对跨域提供支持
     *
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    @Override
    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        HttpServletResponse httpServletResponse = (HttpServletResponse) response;
        httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin"));
        httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
        httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers"));
        // 跨域时会首先发送一个option请求，这里我们给option请求直接返回正常状态
        if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
            httpServletResponse.setStatus(HttpStatus.OK.value());
            return false;
        }
        return super.preHandle(request, response);
    }

    /**
     * 过滤不需要登录校验的请求
     *
     * @param request HttpServletRequest 用于获取登录信息
     * @return true:执行过滤 false:不执行过滤
     */
    private boolean requestIgnore(HttpServletRequest request) {
        List<String> list = authConfig.getTokenExclusionUrl();//过滤url
        for (String url : list) {
            if (url.contains("/**")) {//替换/**
                url = url.replace("/**", "");
            }
            if (request.getRequestURI().contains(url)) {
                return true;
            }
        }
        return false;
    }
}
